﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Windows;
using System.Collections;
using System.ComponentModel;
using Silvermoon.UI;
using Silvermoon.Utils;
using System.Drawing;
using Silvermoon.Core;
using Silvermoon.Windows.Styles;
using Silvermoon.Shapes;

namespace Silvermoon.Controls
{

    /// <summary>
    /// Controls generation an disposal of virtual item container to visualize data in a <see cref="ListView"/>.
    /// and provides functionality to group items by descriptors into sections as well as maintaining a group picker to quickly navigate to items of a specific group.
    /// ListViewController is responsible for grouping, sorting and filtering items out of a given data source as well as adding and removing values.
    /// </summary>
    /// <typeparam name="C">Type of <see cref="T:ITemContainer"/> that this controller generates to visualize data.</typeparam>
    /// <typeparam name="V">Tyoe of data values which are controlled.</typeparam>
    public class ListViewController<C, V> : ListViewControllerBase<C> where C : Item
    {
        #region fields
        public static Command OpenGroupPickerCommand = new Command();

        private List<ItemGroupDescriptor<V>> descriptors = new List<ItemGroupDescriptor<V>>();
        private IEnumerable<V> dataSource;
        private List<ItemGroupDescriptor<V>> availableDescriptors = new List<ItemGroupDescriptor<V>>();
        private int itemsPerRow = 1;
        private List<ListViewGroup> groups;
        private IList dataSourceList;

        #endregion
        #region ctor

        public ListViewController(ListView listView)
            : base(listView)
        {
            ItemHeight = 100;
            GroupButtonSize = new Size(80, 80);
            PickerTileSize = new Size(112, 112);
        }

        public override void Dispose()
        {
            base.Dispose();
            ListView.ItemsSource = null;
            DisposeGroups();
        }

        #endregion
        #region props

        protected bool IsBindingList { get; private set; }

        /// <summary>
        /// Gets or sets the number of items to display in a group in the same row.
        /// </summary>
        public int ItemsPerRow
        {
            get { return itemsPerRow; }
            set
            {
                if (value < 1) value = 1;
                if (value != itemsPerRow)
                {
                    itemsPerRow = value;
                }
            }
        }

        /// <summary>
        /// Gets a list with all descriptors which are assigned with at least one item.
        /// </summary>
        public IList<ItemGroupDescriptor<V>> AvailableGroups { get { return availableDescriptors; } }

        #endregion
        #region methods

        private void DisposeGroups()
        {
            if (groups != null)
            {
                foreach (var g in groups) g.Dispose();
                groups.Clear();
                groups = null;
            }
        }

        public ItemGroupDescriptor<V> AddIndex(string title)
        {
            ItemGroupDescriptor<V> gd = new ItemGroupDescriptor<V>(title);
            GroupDescriptors.Add(gd);
            return gd;
        }

        /// <summary>
        /// Gets the list of <see cref="ItemGroupDescriptor"/> for grouping.
        /// </summary>
        public IList<ItemGroupDescriptor<V>> GroupDescriptors { get { return descriptors; } }

        /// <summary>
        /// Gets or sets the data source for the listview.
        /// </summary>
        public IEnumerable<V> DataSource
        {
            get { return dataSource; }
            set
            {
                //if (dataSource != value)
                {
                    IEnumerable<V> oldValue = value;
                    dataSource = value;
                    dataSourceList = value as IList;
                    OnDataSourceChanged(oldValue);
                }
            }
        }

        protected virtual void OnDataSourceChanged(IEnumerable<V> oldValue)
        {
            foreach (var descriptor in descriptors)
            {
                if (descriptor.Items != null) descriptor.Items.Clear();
            }
            ListView.ItemsSource = null;
            availableDescriptors.Clear();
            ListView.ItemHeight = ItemHeight;

            if (dataSource != null)
            {
                if (descriptors.Count > 0)
                {
                    GenerateGroups();
                }
                else
                {
                    IList list = GetList(dataSource);
                    ListView.ItemsSource = list;
                    IsBindingList = list is IBindingList;
                }
            }
        }

        private IList GetList(IEnumerable<V> dataSource)
        {
            IList list = dataSource as IList;
            if (list == null) list = new List<V>(dataSource);
            return list;
        }

        private void GenerateGroups()
        {

            ListView.ItemsSource = null;
            DisposeGroups();
            groups = new List<ListViewGroup>(descriptors.Count);

            foreach (V value in dataSource)
            {
                ItemGroupDescriptor<V> descriptor = OnGetDescriptorFromValue(value);
                if (descriptors != null)
                {
                    List<V> items = descriptor.Items;
                    if (items == null)
                    {
                        items = new List<V>();
                        descriptor.Items = items;
                    }
                    if (descriptor.IsEmpty)
                    {
                        availableDescriptors.Add(descriptor);
                    }
                    items.Add(value);

                }
            }
            foreach (var descriptor in availableDescriptors)
            {
                ListViewGroup group = CreateGroup(descriptor);

                List<V> list = descriptor.Items;
                if (list.Count > 1 && ValueComparer != null) list.Sort(ValueComparer);
                group.ItemsSource = list;
                // group.Attach(groups.Count, group, ListView);
                groups.Add(group);

            }
            ListView.ItemsSource = groups;
        }

        private static Style DefaultGroupButtonStyle = new Style
        {
            Margin = new Margin(2, 2, null, null),
            Color = PhoneColors.Accent,
            Chrome = new BorderShape { BorderSize = 2, Color = Color.Black },
            Background = PhoneColors.Accent
        };

        private ListViewGroup CreateGroup(ItemGroupDescriptor<V> descriptor)
        {
            ListViewGroup group = new ListViewGroup { ItemHeight = this.ItemHeight, ItemsPerRow = this.ItemsPerRow };
            descriptor.Group = group;

            Button btn = new Button
            {
                StyleName = "GroupButton",
                //Margin = new Margin(2, 2, null, null),
                Background = PhoneColors.Accent,
                //                Color = PhoneColors.Accent,
                Size = GroupButtonSize,
                Command = OpenGroupPickerCommand,
                Content = descriptor.Title,
            };
            btn.Style = GroupButtonStyle ?? DefaultGroupButtonStyle;
            group.Header = btn;
            if (GroupStyle != null) group.Style = GroupStyle;
            group.ItemsSource = descriptor.Items;
            return group;
        }

        /// <summary>
        /// Gets the descriptor from a specified value.
        /// </summary>
        /// <param name="value">Value for which to get the associated descriptor.</param>
        /// <returns>descriptor for the value.</returns>
        protected virtual ItemGroupDescriptor<V> OnGetDescriptorFromValue(V value)
        {
            if (GetDescriptorFromValue != null)
            {
                GroupDescriptorEventArgs<V> e = new GroupDescriptorEventArgs<V>();
                e.Descriptor = null;
                e.GroupDescriptors = descriptors;
                e.Value = value;
                GetDescriptorFromValue(this, e);
                return e.Descriptor;
            }
            return descriptors.Count > 0 ? descriptors[0] : null;
        }

        protected override void OnListViewCommand(object sender, CommandEventArgs e)
        {
            if (e.Command == OpenGroupPickerCommand)
            {
                OnOpenGroupPicker();
                e.Handled = true;
            }
            base.OnListViewCommand(sender, e);
        }

        /// <summary>
        /// Shows the group picker with all groups with the available groups enabled and all other groups disabled to pick a group to be shown in the listview which is controlled.
        /// </summary>
        protected virtual void OnOpenGroupPicker()
        {
            ShowGroupPicker();
        }

        private void ShowGroupPicker()
        {
            Window w = new Window();

            w.Back += (s, e) => w.Hide();
            w.Color = ColorUtil.FromArgb(200, 0, 0, 0);
            w.Style = GroupPickerWindowStyle;

            Screen screen = Screen.Current;
            int tileWidth = Math.Max(32, PickerTileSize.Width);
            int n = Math.Max(1, tileWidth);
            int tileHeight = Math.Max(32, PickerTileSize.Height);
            int numTiles = screen.Width / n;
            int numRows = (descriptors.Count + numTiles - 1) / numTiles;

            int mx = Math.Max(0, (screen.Width - (tileWidth * numTiles)) / 2);
            int my = Math.Max(0, (screen.Height - (tileHeight * numRows)) / 2);
            ListView view = new ListView { Margin = new Margin(mx, my, mx, my) };
            ListViewGroup group = new ListViewGroup { ItemsPerRow = numTiles, ItemHeight = tileHeight };
            ListViewGroup[] groups = new ListViewGroup[] { group };
            view.ItemsSource = groups;
            view.TapItem += new EventHandler<ItemEventArgs>(view_TapItem);
            w.Controls.Add(view);
            ListViewController<GroupPickerItem, ItemGroupDescriptor<V>> controlGenerator = new ListViewController<GroupPickerItem, ItemGroupDescriptor<V>>(view);
            controlGenerator.UpdateItem += new EventHandler<ItemControlGeneratorEventArgs<GroupPickerItem>>(OnUpdateGroup);
            group.ItemsSource = descriptors;
            w.Show();
            w.Hidden += (s, e) =>
                {
                    w.Dispose();
                    group.Dispose();
                };
        }

        private void view_TapItem(object sender, ItemEventArgs e)
        {
            if (e.Item.Host is ListViewGroup)
            {
                if (e.Index >= 0 && e.Index < descriptors.Count)
                {
                    ItemGroupDescriptor<V> selectedDescriptor = descriptors[e.Index];
                    int index = availableDescriptors.IndexOf(selectedDescriptor);
                    if (index >= 0)
                    {
                        Rectangle rect = ListView.GetItemRect(index);
                        ListView.YOffset = rect.Top;
                    }
                }
                ListView lv = sender as ListView;
                lv.Window.Hide();
            }
        }

        protected virtual void OnUpdateGroup(object sender, ItemControlGeneratorEventArgs<GroupPickerItem> e)
        {
            GroupPickerItem item = e.Item;
            ItemGroupDescriptor<V> descriptor = item.DataValue as ItemGroupDescriptor<V>;
            item.Text = descriptor.Title;
            item.Style = GroupPickerStyle;
            item.Enabled = !descriptor.IsEmpty;
        }

        /// <summary>
        /// Inserts a new value. Note that this is not supported with grouping.
        /// </summary>
        /// <param name="index">Index where to add.</param>
        /// <param name="value">Value  to add.</param>
        public virtual void InsertValue(int index, V value)
        {
            if (dataSource == null) throw new NullReferenceException("DataSource is null.");
            if (descriptors.Count > 0) throw new NotSupportedException("InsertValue is not supported with groups.");
            OnValueAdded(value);

            IList<V> list = ListView.ItemsSource as IList<V>;
            list.Insert(index, value);
            if (!IsBindingList) ListView.OnItemInserted(index);

        }

        /// <summary>
        /// Adds a new value.
        /// </summary>
        /// <param name="value">Value to add.</param>
        public virtual void AddValue(V value)
        {
            if (dataSource == null) throw new NullReferenceException("DataSource is null.");
            if (descriptors.Count > 0) AddValueToGroups(value);
            else AddValueToView(value);
            OnValueAdded(value);
        }

        private void AddValueToView(V value)
        {
            IList<V> list = ListView.ItemsSource as IList<V>;
            int index = list.IndexOf(value);
            if (index < 0)
            {
                list.Add(value);
                index = list.Count - 1;
            }
            if (!IsBindingList) ListView.OnItemInserted(index);
        }

        private void AddValueToGroups(V value)
        {
            ItemGroupDescriptor<V> descriptor = OnGetDescriptorFromValue(value);
            if (descriptor != null)
            {
                if (descriptor.Items != null && descriptor.Items.Count > 0)
                {
                    AddValueToExistingGroup(value, descriptor);
                }
                else
                {
                    AddValueToNewGroup(value, descriptor);
                }
            }
        }

        private void AddValueToNewGroup(V value, ItemGroupDescriptor<V> descriptor)
        {
            ListViewGroup group = CreateGroup(descriptor);
            descriptor.Group = group;

            List<V> items = descriptor.Items;
            if (items == null)
            {
                items = new List<V>();
                descriptor.Items = items;
            }
            group.ItemsSource = items;
            int groupIndex = descriptors.IndexOf(descriptor);
            groupIndex = GetAvailableIndexFromAllIndex(groupIndex);
            groups.Insert(groupIndex, group);
            availableDescriptors.Insert(groupIndex, descriptor);
            ListView.OnItemInserted(groupIndex);
            items.Add(value);
            group.OnItemInserted(0);
        }

        private int GetAvailableIndexFromAllIndex(int index)
        {
            if (index == 0) return 0;
            else if (index == descriptors.Count - 1) return availableDescriptors.Count;

            ItemGroupDescriptor<V> previous = descriptors[--index];
            while (previous.IsEmpty && index > 0) previous = descriptors[--index];
            int n = availableDescriptors.Count;
            for (int i = 0; i < n; i++)
            {
                if (availableDescriptors[i] == previous) return i + 1;
            }
            return n;
        }

        private void AddValueToExistingGroup(V value, ItemGroupDescriptor<V> descriptor)
        {
            List<V> items = descriptor.Items;
            int count = items.Count;
            int index;
            if (count > 1 && ValueComparer != null)
            {
                index = BinarySearchLeEq(value, items) + 1;
            }
            else
            {
                index = count;
            }
            var group = descriptor.Group;
            group.Insert(index, value);
        }

        /// <summary>
        /// Performs a quick binary search for less or equal.
        /// </summary>
        private int BinarySearchLeEq(V value, List<V> values)
        {
            int max = values.Count;
            if (max == 0) return 0;
            int min = 0;
            int i = max >> 1;

            while (min < max)
            {
                var item = values[i];
                int dif = ValueComparer(item, value);

                if (dif == 0) return i;
                if (dif < 0) min = i + 1;
                else max = i;
                i = (min + max) >> 1;
            }
            return i - 1;
        }

        /// <summary>
        /// Removes the specified value.
        /// </summary>
        /// <param name="value">Value to remove.</param>
        public virtual void RemoveValue(V value)
        {
            if (dataSource == null) throw new NullReferenceException("DataSource is null.");
            if (descriptors.Count > 0) RemoveValueFromGroups(value);
            else RemoveValueFromView(value);

            IList list = dataSourceList;
            if (list != null) list.Remove(value);

            OnValueRemoved(value);
        }

        private void RemoveValueFromView(V value)
        {
            IList<V> list = ListView.ItemsSource as IList<V>;
            int index = list.IndexOf(value);
            if (index >= 0)
            {
                list.RemoveAt(index);
                if (!IsBindingList) this.ListView.OnItemRemoved(index);
            }
        }

        protected virtual void OnValueRemoved(V value)
        {
            if (ValueRemoved != null) ValueRemoved(this, new ListViewControllerEventArgs<V>(value));
        }

        protected virtual void OnValueAdded(V value)
        {
            if (ValueAdded != null) ValueAdded(this, new ListViewControllerEventArgs<V>(value));
        }

        private void RemoveValueFromGroups(V value)
        {
            var descriptor = OnGetDescriptorFromValue(value);
            if (descriptor != null && descriptor.Items != null)
            {
                var group = descriptor.Group;
                if (group != null)
                {
                    List<V> items = descriptor.Items;
                    int index = items.IndexOf(value);
                    if (index >= 0)
                    {
                        if (items.Count == 1)
                        {
                            RemoveAvailableDescriptor(descriptor);
                        }
                        else
                        {
                            group.RemoveAt(index);
                        }
                    }
                }

            }
        }

        private void RemoveAvailableDescriptor(ItemGroupDescriptor<V> descriptor)
        {
            ListViewGroup group = descriptor.Group;
            int i = availableDescriptors.IndexOf(descriptor);
            group.ItemsSource = null;
            descriptor.Items.Clear();
            RemoveGroupFromListView(group);
            availableDescriptors.RemoveAt(i);
            descriptor.Group = null;
        }

        private void RemoveGroupFromListView(ListViewGroup group)
        {
            int groupIndex = groups.IndexOf(group);
            if (groupIndex < 0) return;
            this.groups.RemoveAt(groupIndex);
            this.ListView.OnItemRemoved(groupIndex);
            group.Dispose();
        }

        /// <summary>
        /// Binds a given value with the item, if it is attached to the listview.
        /// </summary>
        public void BindValue(V value)
        {
            if (value == null) throw new ArgumentNullException("value");
            foreach (Item item in ListView.AttachedItems)
            {
                ListViewGroup group = item as ListViewGroup;
                if (group != null) BindGroupValue(group, value);
                else
                {
                    if (value.Equals(item.DataValue))
                    {
                        C typedItem = item as C;
                        if (typedItem != null)
                        {
                            OnUpdateItem(typedItem, item.ItemIndex, item.DataValue, item.Host as ListViewGroup);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Binds the values with all currently attached items in the listview.
        /// This causes each visible item to be executed against UpdateItem.
        /// </summary>
        public void BindItems()
        {
            foreach (Item item in ListView.AttachedItems)
            {
                ListViewGroup group = item as ListViewGroup;
                if (group != null) BindItemGroups(group);
                else
                {
                    C typedItem = item as C;
                    if (typedItem != null)
                    {
                        OnUpdateItem(typedItem, item.ItemIndex, item.DataValue, item.Host as ListViewGroup);
                    }
                }
            }
        }

        private void BindItemGroups(ListViewGroup group)
        {
            foreach (Item item in group.AttachedItems)
            {
                C typedItem = item as C;
                if (typedItem != null)
                {
                    OnUpdateItem(typedItem, item.ItemIndex, item.DataValue, item.Host as ListViewGroup);
                }
            }
        }

        private void BindGroupValue(ListViewGroup group, V value)
        {
            foreach (Item item in group.AttachedItems)
            {
                if (value.Equals(item.DataValue))
                {
                    C typedItem = item as C;
                    if (typedItem != null)
                    {
                        OnUpdateItem(typedItem, item.ItemIndex, item.DataValue, item.Host as ListViewGroup);
                    }
                }
            }
        }


        #endregion
        #region events

        /// <summary>
        /// Gets the descriptor from a data value. Note that the descriptors must prevously be added to GroupDescriptors.
        /// The event handler must determine the approproate Descriptor from the given list in <seealso cref="GroupDescriptorEventArgs"/> and set the determined
        /// value.
        /// </summary>
        public event EventHandler<GroupDescriptorEventArgs<V>> GetDescriptorFromValue;

        /// <summary>
        /// Occurs when a value was added to the list.
        /// </summary>
        public event EventHandler<ListViewControllerEventArgs<V>> ValueAdded;

        /// <summary>
        /// Occurs when a value was removed from the list.
        /// </summary>
        public event EventHandler<ListViewControllerEventArgs<V>> ValueRemoved;

        /// <summary>
        /// Gets or sets the comparer to compare the values of two items.
        /// </summary>
        public Comparison<V> ValueComparer;

        #endregion
    }
}
